package org.intellij.markdown.parser.constraints

import org.intellij.markdown.MarkdownTokenTypes
import org.intellij.markdown.flavours.gfm.GFMTokenTypes
import org.intellij.markdown.parser.LookaheadText
import org.intellij.markdown.parser.ProductionHolder
import org.intellij.markdown.parser.constraints.BaseMarkdownConstraints.Companion.BQ_CHAR
import org.intellij.markdown.parser.constraints.BaseMarkdownConstraints.Companion.create
import org.intellij.markdown.parser.markerblocks.providers.HorizontalRuleProvider
import org.intellij.markdown.parser.sequentialparsers.SequentialParser
import kotlin.math.min


interface BlockCheck {
    fun check(body:MutableMarkdownConstraints,pos: LookaheadText.Position):BaseMarkdownConstraints?

    fun populateConstraintsTokens(
        body: MutableMarkdownConstraints,
        pos: LookaheadText.Position,
        offset:Int,
        productionHolder: ProductionHolder)

    fun checkType(char:Char):Boolean
}

class MutableMarkdownConstraints(indents: IntArray,
                                 types: CharArray,
                                 isExplicit: BooleanArray,
                                 charsEaten: Int,
                                 private val isCheckbox: Boolean,
                                 val blockMap: MutableList<BlockCheck> = mutableListOf<BlockCheck>()
) : BaseMarkdownConstraints(indents, types, isExplicit, charsEaten) {

    override val base: BaseMarkdownConstraints
        get() = BASE

    fun hasCheckbox(): Boolean {
        return isCheckbox
    }

    override fun createNewConstraints(indents: IntArray, types: CharArray, isExplicit: BooleanArray, charsEaten: Int): BaseMarkdownConstraints {
        val initialType = types[types.size - 1]
        val originalType = toOriginalType(initialType)
        types[types.size - 1] = originalType
        return MutableMarkdownConstraints(indents, types, isExplicit, charsEaten, initialType != originalType)
    }

    override fun addModifierIfNeeded(pos: LookaheadText.Position?): BaseMarkdownConstraints? {
        if (pos == null || pos.offsetInCurrentLine == -1)
            return null
        if (HorizontalRuleProvider.isHorizontalRule(pos.currentLine, pos.offsetInCurrentLine)) {
            return null
        }
        blockMap.forEach {
            it.check(this,pos)?.let { find->
                return find
            }
        }
        return null
    }

    override fun fetchListMarker(pos: LookaheadText.Position): ListMarkerInfo? {
        val baseMarkerInfo = super.fetchListMarker(pos)
            ?: return null

        val line = pos.currentLine
        var offset = pos.offsetInCurrentLine + baseMarkerInfo.markerLength

        while (offset < line.length && (line[offset] == ' ' || line[offset] == '\t')) {
            offset++
        }

        if (offset + 3 <= line.length
            && line[offset] == '['
            && line[offset + 2] == ']'
            && (line[offset + 1] == 'x' || line[offset + 1] == 'X' || line[offset + 1] == ' ')) {
            return ListMarkerInfo(offset + 3 - pos.offsetInCurrentLine,
                toCheckboxType(baseMarkerInfo.markerType),
                baseMarkerInfo.markerLength)
        } else {
            return baseMarkerInfo
        }
    }

    fun populateConstraintsTokens(pos: LookaheadText.Position,
                                  offset:Int,
                                  productionHolder: ProductionHolder) {
//        val line = pos.currentLine
//        var offset = pos.offsetInCurrentLine
//        while (offset < line.length && line[offset] != '[') {
//            offset++
//        }
//        if (offset == line.length) {
//            super.populateConstraintsTokens(pos, constraints, productionHolder)
//            return
//        }

        val type = when (types.lastOrNull()) {
            '>' ->
                MarkdownTokenTypes.BLOCK_QUOTE
            '.', ')' ->
                MarkdownTokenTypes.LIST_NUMBER
            else ->
                MarkdownTokenTypes.LIST_BULLET
        }
        val middleOffset = pos.offset - pos.offsetInCurrentLine + offset

        val endOffset = min(pos.offset - pos.offsetInCurrentLine + getCharsEaten(pos.currentLine),
                             pos.nextLineOrEofOffset)

        productionHolder.addProduction(listOf(
            SequentialParser.Node(pos.offset..middleOffset, type),
            SequentialParser.Node(middleOffset..endOffset, GFMTokenTypes.CHECK_BOX)
        ))
    }

    companion object {
        val blockMap = mutableListOf<BlockCheck>(ListItemAdd,BlockQuote)

        val BASE: MutableMarkdownConstraints = MutableMarkdownConstraints(IntArray(0), CharArray(0), BooleanArray(0), 0, false)

        private fun toCheckboxType(originalType: Char): Char {
            return (originalType.code + 100).toChar()
        }

        private fun toOriginalType(checkboxType: Char): Char {
            if (checkboxType.code < 128) {
                return checkboxType
            }
            return (checkboxType.code - 100).toChar()
        }
    }
}

val ListItemAdd:BlockCheck get() = object : BlockCheck {
    override fun check(
        body: MutableMarkdownConstraints,
        pos: LookaheadText.Position,
    ): BaseMarkdownConstraints? {
        val line = pos.currentLine

        var offset = pos.offsetInCurrentLine
        var spacesBefore = if (offset > 0 && line[offset - 1] == '\t')
            (4 - body.indent % 4) % 4
        else
            0
        // '\t' can be omitted here since it'll add at least 4 indent
        while (offset < line.length && line[offset] == ' ' && spacesBefore < 3) {
            spacesBefore++
            offset++
        }
        if (offset == line.length)
            return null

        val markerInfo = body.fetchListMarker(pos.nextPosition(offset - pos.offsetInCurrentLine)!!)
            ?: return null

        offset += markerInfo.markerLength
        var spacesAfter = 0

        val markerEndOffset = offset
        afterSpaces@
        while (offset < line.length) {
            when (line[offset]) {
                ' ' -> spacesAfter++
                '\t' -> spacesAfter += 4 - spacesAfter % 4
                else -> break@afterSpaces
            }
            offset++
        }

        // By the classification http://spec.commonmark.org/0.20/#list-items
        // 1. Basic case
        if (spacesAfter in 1..4 && offset < line.length) {
            return create(body, spacesBefore + markerInfo.markerIndent + spacesAfter, markerInfo.markerType, true, offset)
        }
        if (spacesAfter >= 5 && offset < line.length // 2. Starts with an indented code
            || offset == line.length) {
            // 3. Starts with an empty string
            return create(body, spacesBefore + markerInfo.markerIndent + 1, markerInfo.markerType, true,
                min(offset, markerEndOffset + 1)
            )
        }

        return null
    }

    override fun populateConstraintsTokens(
        body: MutableMarkdownConstraints,
        pos: LookaheadText.Position,
        offset: Int,
        productionHolder: ProductionHolder,
    ) {
        val type = when (body.types.lastOrNull()) {
            '.', ')' ->
                MarkdownTokenTypes.LIST_NUMBER
            else ->
                MarkdownTokenTypes.LIST_BULLET
        }
        val middleOffset = pos.offset - pos.offsetInCurrentLine + offset

        val endOffset = min(pos.offset - pos.offsetInCurrentLine + body.getCharsEaten(pos.currentLine),
            pos.nextLineOrEofOffset)

        if(middleOffset == pos.offset){
            productionHolder.addProduction(listOf(
                SequentialParser.Node(pos.offset..endOffset, type),
                ))
        }else{
            productionHolder.addProduction(listOf(
                SequentialParser.Node(pos.offset..middleOffset, type),
                SequentialParser.Node(middleOffset..endOffset, GFMTokenTypes.CHECK_BOX)
            ))
        }
    }

    override fun checkType(char: Char): Boolean = char in listOf('.', ')')
}

val BlockQuote:BlockCheck get() = object : BlockCheck {
    override fun check(
        body: MutableMarkdownConstraints,
        pos: LookaheadText.Position,
    ): BaseMarkdownConstraints? {
        val line = pos.currentLine

        var offset = pos.offsetInCurrentLine
        var spacesBefore = 0
        // '\t' can be omitted here since it'll add at least 4 indent
        while (offset < line.length && line[offset] == ' ' && spacesBefore < 3) {
            spacesBefore++
            offset++
        }
        if (offset == line.length || line[offset] != BQ_CHAR) {
            return null
        }
        offset++

        var spacesAfter = 0
        if (offset >= line.length || line[offset] == ' ' || line[offset] == '\t') {
            spacesAfter = 1

            if (offset < line.length) {
                offset++
            }
        }

        return create(body, spacesBefore + 1 + spacesAfter, BQ_CHAR, true, offset)
    }

    override fun populateConstraintsTokens(
        body: MutableMarkdownConstraints,
        pos: LookaheadText.Position,
        offset: Int,
        productionHolder: ProductionHolder,
    ) {
        require(body.types.lastOrNull() == '>')
//        val type = when (body.types.lastOrNull()) {
//            '>' ->
//                MarkdownTokenTypes.BLOCK_QUOTE
//            else ->
//                MarkdownTokenTypes.LIST_BULLET
//        }
        val type = MarkdownTokenTypes.BLOCK_QUOTE

        val middleOffset = pos.offset - pos.offsetInCurrentLine + offset
        val endOffset = min(pos.offset - pos.offsetInCurrentLine + body.getCharsEaten(pos.currentLine),
            pos.nextLineOrEofOffset)

        productionHolder.addProduction(listOf(
            SequentialParser.Node(pos.offset..middleOffset, type),
            SequentialParser.Node(middleOffset..endOffset, GFMTokenTypes.CHECK_BOX)
        ))
    }

    override fun checkType(char: Char): Boolean =  char == '>'

}